package errs

import (
	"errors"
	"fmt"
	"runtime"
	"strings"
)

func NewStackError(format string, a ...interface{}) error {
	builder := strings.Builder{}
	builder.WriteString(fmt.Sprintf(format, a...))
	appendStacks(&builder)
	return errors.New(builder.String())
}

func WrapStack(err error, format string, a ...interface{}) error {
	builder := strings.Builder{}
	builder.WriteString(fmt.Sprintf(format, a...))
	appendStacks(&builder)
	return fmt.Errorf("%w:%s", err, builder.String())
}

func appendStacks(builder *strings.Builder) {
	var (
		stackPC []uintptr
		f       runtime.Frame
		more    bool
		index   int
	)

	// skip func StackError invocations
	stackPC = make([]uintptr, 32)
	count := runtime.Callers(3, stackPC)
	stackPC = stackPC[:count]
	frames := runtime.CallersFrames(stackPC)

	for {
		f, more = frames.Next()
		if index = strings.Index(f.File, "src"); index != -1 {
			// trim GOPATH or GOROOT prifix
			f.File = string(f.File[index+4:])
		}
		frame := fmt.Sprintf("\n  %s\n    %s:%d", f.Function, f.File, f.Line)
		builder.WriteString(frame)
		if !more || f.Function == "main.main" {
			break
		}
	}
}
